package vn.tictok.facebook;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.RequestAcceptEncoding;
import org.apache.http.client.protocol.ResponseContentEncoding;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FacebookServiceImpl implements FacebookService {
	private Logger logger = LoggerFactory.getLogger(FacebookServiceImpl.class);
	private HttpClient httpClient;
	private HttpClient loginClient;
	private Map<String, String> idToAccessToken;
	
	public FacebookServiceImpl() {
		idToAccessToken = new ConcurrentHashMap<String, String>();
		httpClient = createHttpClient();
		loginClient = createHttpClient();
	}
	
	@Override
	public JSONObject loginByToken(String facebookId, String accessToken) {
		JSONObject result = new JSONObject();
		String profileId = getProfileId(accessToken);
		if (facebookId.equals(profileId)) {
			result.put("result", true);
			result.put("fbId", profileId);
			result.put("token", accessToken);
			idToAccessToken.put(profileId, accessToken);
			return result;
		}
		return null;
	}
	
	@Override
	public JSONObject login(String email, String password) {
		JSONObject result = new JSONObject();
		try {
			String accessToken = LoginTask.login(loginClient, email, password);
			if (accessToken != null) {
				String profileId =  getProfileId(accessToken);
				if (profileId != null) {
					result.put("result", true);
					result.put("fbId", profileId);
					result.put("token", accessToken);
					idToAccessToken.put(profileId, accessToken);
				} else {
					result.put("result", false);
					result.put("exception", "Cannot find fb id");
				}
			} else {
				result.put("result", false);
			}
		} catch (Exception e) {
			result.put("result", false);
			result.put("exception", e.toString());
		}
		return result;
	}

	@Override
	public JSONObject putObject(String requestId, String parent, String name, Map<String, String> data) {
		String accessToken = idToAccessToken.get(requestId);
		if (accessToken != null) {
			return request(FacebookConstant.GRAPH_URL + parent + "/" + name, accessToken, null, data);
		}
		return null;
	}
	
	@Override
	public JSONObject requestConnection(String requestId, String id,
			String connectionName, Map<String, String> args) {
		String accessToken = idToAccessToken.get(requestId);
		if (accessToken != null) {
			return request(FacebookConstant.GRAPH_URL + id + "/" + connectionName, accessToken, args, null);
		}
		return null;
	}

	@Override
	public JSONObject requestObject(String requestId, String id, Map<String, String> args) {
		String accessToken = idToAccessToken.get(requestId);
		if (accessToken != null) {
			return request(FacebookConstant.GRAPH_URL + id, accessToken, args, null);
		}
		return null;
	}

	@Override
	public JSONObject requestObjects(String requestId, String[] ids, Map<String, String> args) {
		String accessToken = idToAccessToken.get(requestId);
		if (accessToken != null) {
			StringBuilder sb = new StringBuilder();
			for (String id : ids) {
				sb.append(id);
				sb.append(",");
			}
			args.put("ids", sb.toString().substring(0, sb.length() - 1));
			return request(FacebookConstant.GRAPH_URL, accessToken, args, null);
		}
		return null;
	}
	
	@Override
	public JSONObject postWall(String requestId, String profileId, String wall) {
		Map<String, String> data = new HashMap<String, String>();
		data.put("message", wall);
		return putObject(requestId, profileId, "feed", data);
	}
	
	@Override
	public JSONObject postComment(String requestId, String objectId, String comment) {
		Map<String, String> data = new HashMap<String, String>();
		data.put("message", comment);
		return putObject(requestId, objectId, "comments", data);
	}
	
	@Override
	public JSONObject like(String requestId, String objectId) {
		return putObject(requestId, objectId, "likes", new HashMap<String, String>());
	}
	
	@Override
	public JSONObject unlike(String requestId, String objectId) {
		String accessToken = idToAccessToken.get(requestId);
		if (accessToken != null) {
			Map<String, String>	args = new HashMap<String, String>();
			args.put("method", "delete");
			
			Map<String, String>	postArgs = new HashMap<String, String>();
			postArgs.put("access_token", accessToken);
			try {
				return doPost(FacebookConstant.GRAPH_URL + objectId + "/likes", args, postArgs);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	@Override
	public JSONObject delete(String requestId, String objectId) {
		String accessToken = idToAccessToken.get(requestId);
		if (accessToken != null) {
			Map<String, String>	args = new HashMap<String, String>();
			args.put("method", "delete");
			
			Map<String, String>	postArgs = new HashMap<String, String>();
			postArgs.put("access_token", accessToken);
			try {
				return doPost(FacebookConstant.GRAPH_URL + objectId, args, postArgs);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	@Override
	public JSONObject doFql(String requestId, String query) {
		String accessToken = idToAccessToken.get(requestId);
		if (accessToken != null) {
			Map<String, String>	args = new HashMap<String, String>();
			args.put("format", "json");
			args.put("query", query);
			args.put("access_token", accessToken);
			try {
				return doGet(FacebookConstant.FQL_URL, args);
			} catch (Exception e) {
				logger.error("Exception when request " + FacebookConstant.FQL_URL, e);
				return null;
			} 
		}
		return null;
	}

	@Override
	public void logout(String profileId) {
		idToAccessToken.remove(profileId);
	}
	
	private JSONObject request(String path, String accessToken, Map<String, String> args, Map<String, String> postArgs) {
		if (postArgs != null) {
			postArgs.put("access_token", accessToken);
		} else {
			args.put("access_token", accessToken);
		}
		try {
			if (postArgs != null) {
				return doPost(path, args, postArgs);
			} else {
				return doGet(path, args);
			}
		} catch (Exception e) {
			logger.error("Exception when request " + path, e);
		}
		return null;
	}
	
	private JSONObject doPost(String path, Map<String, String> args,
			Map<String, String> postArgs) throws ClientProtocolException, IOException, JSONException {
		if (args != null) {
			List<NameValuePair> queryData = new ArrayList<NameValuePair>(args.size());
			for (String key : args.keySet()) {
				queryData.add(new BasicNameValuePair(key, args.get(key)));
			}
			if (!path.endsWith("?")) {
				path += "?";
			}
			path += URLEncodedUtils.format(queryData, "UTF-8");
		}
		HttpPost post = new HttpPost(path);
		List<NameValuePair> postData = new ArrayList<NameValuePair>(postArgs.size());
		for (String key : postArgs.keySet()) {
			postData.add(new BasicNameValuePair(key, postArgs.get(key)));
		}
		post.setEntity(new UrlEncodedFormEntity(postData, HTTP.UTF_8));
		HttpResponse response = httpClient.execute(post);
		HttpEntity entity = response.getEntity();
		if (entity != null) {
			String responseStr = EntityUtils.toString(entity, "UTF-8");
			try {
				switch (responseStr.charAt(0)) {
					case '{':
						return new JSONObject(responseStr);
					case '[':
						return new JSONArray(responseStr);
					default:
						boolean res = Boolean.parseBoolean(responseStr);
						JSONObject result = new JSONObject();
						result.put("result", res);
						return result;
				}
			} catch (JSONException e) {
				logger.error("Cannot convert to JSON: " + responseStr, e);
				return null;
			}
		}
		return null;
	}
	
	private JSONObject doGet(String path, Map<String, String> args) throws ClientProtocolException, IOException, JSONException {
		if (args != null) {
			List<NameValuePair> queryData = new ArrayList<NameValuePair>(args.size());
			for (String key : args.keySet()) {
				queryData.add(new BasicNameValuePair(key, args.get(key)));
			}
			if (!path.endsWith("?")) {
				path += "?";
			}
			path += URLEncodedUtils.format(queryData, "UTF-8");
		}
		HttpGet get = new HttpGet(path);
		HttpResponse response = httpClient.execute(get);
		HttpEntity entity = response.getEntity();
		if (entity != null) {
			String responseStr = EntityUtils.toString(entity, "UTF-8");
			try {
				switch (responseStr.charAt(0)) {
					case '{':
						return new JSONObject(responseStr);
					case '[':
						return new JSONArray(responseStr);
					default:
						boolean res = Boolean.parseBoolean(responseStr);
						JSONObject result = new JSONObject();
						result.put("result", res);
						return result;
				}
			} catch (JSONException e) {
				logger.error("Cannot convert to JSON: " + responseStr, e);
				return null;
			}
		}
		return null;
	}
	
	private String getProfileId(String accessToken) {
		Map<String, String> args = new HashMap<String, String>();
		args.put("fields", "id");
		JSONObject idResults = request(FacebookConstant.ME_URL, accessToken, args, null);
		if (idResults != null) {
			return idResults.getString("id");
		}
		return null;
	}
	
	@Override
	public JSONObject getNewFeeds(String requestId, int offset, int limit) {
		Map<String, String>	args = new HashMap<String, String>();
		args.put("offset", new Integer(offset).toString());
		args.put("limit", new Integer(limit).toString());
		return requestConnection(requestId, requestId, "home", args);
	}

	@Override
	public JSONObject getNewFeedsByFql(String requestId, int offset, int limit) {
		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append("SELECT post_id, actor_id, target_id, message, attachment, comments, likes FROM stream WHERE filter_key in (SELECT filter_key FROM stream_filter WHERE uid=");
		queryBuilder.append(requestId);
		queryBuilder.append(" AND type='newsfeed') AND is_hidden = 0 LIMIT ");
		queryBuilder.append(limit);
		queryBuilder.append(" OFFSET ");
		queryBuilder.append(offset);
		return doFql(requestId, queryBuilder.toString());
	}
	
	@Override
	public JSONObject getWall(String requestId, String wallId, int offset, int limit) {
		Map<String, String>	args = new HashMap<String, String>();
		args.put("offset", new Integer(offset).toString());
		args.put("limit", new Integer(limit).toString());
		return requestConnection(requestId, wallId, "feed", args);
	}

	@Override
	public JSONObject getWallByFql(String requestId, String wallId, int offset, int limit) {
		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append("SELECT post_id, actor_id, target_id, message, attachment, comments, likes FROM stream WHERE source_id=");
		queryBuilder.append(wallId);
		queryBuilder.append(" LIMIT ");
		queryBuilder.append(limit);
		queryBuilder.append(" OFFSET ");
		queryBuilder.append(offset);
		return doFql(requestId, queryBuilder.toString());
	}
	
	@Override
	public JSONObject getComments(String requestId, String objectId) {
		Map<String, String> args = new HashMap<String, String>();
		args.put("fields", "id, comments");
		return requestObject(requestId, objectId, args);
	}
	
	@Override
	public JSONObject getFriends(String requestId, int offset, int limit) {
		Map<String, String>	args = new HashMap<String, String>();
		args.put("offset", new Integer(offset).toString());
		args.put("limit", new Integer(limit).toString());
		return requestConnection(requestId, requestId, "friends", args);
	}

	@Override
	public JSONObject getPhotoAlbums(String requestId, String profileId,
			int offset, int limit) {
		Map<String, String>	args = new HashMap<String, String>();
		args.put("offset", new Integer(offset).toString());
		args.put("limit", new Integer(limit).toString());
		return requestConnection(requestId, profileId, "albums", args);
	}
	
	@Override
	public JSONObject getPhotosInAlbum(String requestId, String albumId, int offset, int limit) {
		Map<String, String>	args = new HashMap<String, String>();
		args.put("offset", new Integer(offset).toString());
		args.put("limit", new Integer(limit).toString());
		return requestConnection(requestId, albumId, "photos", args);
	}
	
	@Override
	public JSONObject getPhotos(String requestId, String profileId, int offset,
			int limit) {
		Map<String, String>	args = new HashMap<String, String>();
		args.put("offset", new Integer(offset).toString());
		args.put("limit", new Integer(limit).toString());
		return requestConnection(requestId, profileId, "photos", args);
	}

	@Override
	public JSONObject getProfile(String requestId, String profileId) {
		return requestObject(requestId, profileId, null);
	}

	@Override
	public JSONObject getFriendRequest(String requestId) {
		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append("SELECT uid_from FROM friend_request WHERE uid_to=");
		queryBuilder.append(requestId);
		return doFql(requestId, queryBuilder.toString());
	}

	@Override
	public JSONObject getNotifications(String requestId) {
		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append("SELECT notification_id, sender_id, title_text, body_text, href FROM notification WHERE recipient_id=");
		queryBuilder.append(requestId);
		queryBuilder.append("AND is_unread = 1 AND is_hidden = 0");
		return doFql(requestId, queryBuilder.toString());
	}

	@SuppressWarnings("deprecation")
	private HttpClient createHttpClient() {
		SchemeRegistry schemeRegistry = new SchemeRegistry();
		schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
		schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
		ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
		DefaultHttpClient client = new DefaultHttpClient(cm);
		client.addRequestInterceptor(new RequestAcceptEncoding());
		client.addResponseInterceptor(new ResponseContentEncoding());
		HttpProtocolParams.setUserAgent(client.getParams(), "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2");
		cm.setMaxTotal(1000);
		SSLSocketFactory sf = (SSLSocketFactory) client.getConnectionManager().getSchemeRegistry().getScheme("https").getSchemeSocketFactory();
		sf.setHostnameVerifier(new AllowAllHostnameVerifier());
		return client;
	}
}
